package controller

import (
	"aqchain/pkg/entity"
	"aqchain/pkg/log"
	"aqchain/pkg/model"
	"errors"
	"fmt"
	"gitee.com/aqchain/go-ethereum/common"
	"gitee.com/aqchain/go-ethereum/common/hexutil"
	"gitee.com/aqchain/go-ethereum/crypto"
	"gitee.com/aqchain/go-ethereum/signer/core"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"net/http"
	"strconv"
	"strings"
)

// CreateAccount 创建账户信息
func (c *Controller) CreateAccount(ctx *gin.Context) {
	// 绑定参数
	param := entity.PostAccountForm{}
	err := ctx.BindJSON(&param)
	if err != nil {
		log.Logger.Debug("error", zap.Error(err), zap.String("code", errParams.Error()))
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errParams))
		return
	}
	account := model.Account{
		Address:  param.Address,
		Mail:     param.Mail,
		Username: param.Username,
	}
	// 验证签名
	err = c.verify(account, param.Signature)
	if err != nil {
		log.Logger.Debug("error", zap.Error(err), zap.String("code", errSignature.Error()))
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errSignature))
		return
	}
	// 保存账户数据
	err = c.db.Save(&account).Error
	if err != nil {
		log.Logger.Debug("error", zap.String("error", err.Error()), zap.String("code", errDBError.Error()))
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(errDBError))
		return
	}
	// 返回结果
	ctx.JSON(http.StatusOK, SuccessResponse(account))
}

// GetAccount 获取钱包账户信息
func (c *Controller) GetAccount(ctx *gin.Context) {
	// 绑定参数
	param := entity.GetAccountUri{}
	err := ctx.BindUri(&param)
	if err != nil {
		log.Logger.Debug("error", zap.Error(err), zap.String("code", errParams.Error()))
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errParams))
		return
	}
	// 获取账户信息
	record := model.Account{Address: param.Address}
	err = c.db.Where(&record).First(&record).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			ctx.JSON(http.StatusBadRequest, ErrorResponse(errAccountNotExist))
		} else {
			log.Logger.Debug("error", zap.String("error", err.Error()), zap.String("code", errDBError.Error()))
			ctx.JSON(http.StatusInternalServerError, ErrorResponse(errDBError))
		}
		return

	}
	result := entity.Account{
		Address:    record.Address,
		Mail:       record.Mail,
		Username:   record.Username,
		AlipayID:   record.AlipayID,
		AlipayName: record.AlipayName,
		Nonce:      record.Nonce,
	}
	// 返回结果
	ctx.JSON(http.StatusOK, SuccessResponse(result))
}

// UpdateAccount 修改账户信息
func (c *Controller) UpdateAccount(ctx *gin.Context) {
	// 绑定参数
	param := entity.PutAccountForm{}
	err := ctx.BindJSON(&param)
	if err != nil {
		log.Logger.Debug("error", zap.Error(err), zap.String("code", errParams.Error()))
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errParams))
		return
	}
	var account model.Account
	c.db.Where(&model.Account{Address: param.Address}).First(&account)
	// 验证签名
	err = c.verify(account, param.Signature)
	if err != nil {
		log.Logger.Debug("error", zap.Error(err), zap.String("code", errSignature.Error()))
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errSignature))
		return
	}
	// 修改
	account.Mail = param.Mail
	account.Username = param.Username
	account.AlipayID = param.AlipayID
	account.AlipayName = param.AlipayName
	tx := c.db.Updates(&account)
	if tx.RowsAffected == 0 {
		if tx.Error != nil {
			log.Logger.Debug("error", zap.Error(err), zap.String("code", errDBError.Error()))
			ctx.JSON(http.StatusInternalServerError, ErrorResponse(errDBError))
			return
		}
		ctx.JSON(http.StatusBadRequest, ErrorResponse(errNoEffectRecord))
		return
	} else {
		c.db.Where(&model.Account{Address: param.Address}).First(&account)
		ctx.JSON(http.StatusOK, SuccessResponse(account))
	}
}

func (c *Controller) verify(account model.Account, signature string) error {
	msg := "0x"
	if account.Nonce != 0 {
		msg = strconv.FormatUint(account.Nonce, 10)
	}
	recoveredAddress, err := recover(msg, signature)
	if err != nil {
		return err
	}
	if strings.ToLower(account.Address) != strings.ToLower(recoveredAddress.String()) {
		return errSignature
	}
	// 如果验证没有错误 修改账户Nonce值
	c.db.Select("Nonce").Updates(&account)
	return nil
}

// recover 以太坊恢复签名地址
func recover(msg, hexSig string) (common.Address, error) {
	sig, err := hexutil.Decode(hexSig)
	if err != nil {
		return common.Address{}, err
	}

	if len(sig) != 65 {
		return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
	}
	if sig[64] != 27 && sig[64] != 28 {
		return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
	}
	sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1

	hash, _ := core.SignHash([]byte(msg))
	rpk, err := crypto.Ecrecover(hash, sig)
	if err != nil {
		return common.Address{}, err
	}
	pubKey := crypto.ToECDSAPub(rpk)
	recoveredAddr := crypto.PubkeyToAddress(*pubKey)
	return recoveredAddr, nil
}
